-module(vernemq_plugin).
-behaviour(auth_on_register_hook).
-behaviour(auth_on_subscribe_hook).
-behaviour(auth_on_publish_hook).
-behaviour(on_client_wakeup_hook).
-behaviour(on_client_offline_hook).
-behaviour(on_client_gone_hook).
-behaviour(on_offline_message_hook).
-behaviour(on_register_hook).
-behaviour(on_subscribe_hook).
-behaviour(on_unsubscribe_hook).


-define(AUTH_KEY, "lbschatiZ28gybunzmZ").
-define(UINT, 32/unsigned-big-integer).
-define(UBYTE, 8/unsigned-big-integer).
-define(VERSION,<<1,0,0>>).

-define(CMD_REGISTER,1).
-define(CMD_LOGIN,2).
-define(CMD_TOKEN,3).
-define(CMD_ADD_FRIEND,4).
-define(CMD_SEND_MSG,9).
-define(CMD_SEND_MSG_REPLY,19).
-define(CMD_ADD_FRIEND_REPLY,15).
-define(CMD_UNACK_FRIEND,14).
-define(CMD_UNACK_FRIEND_REPLY,16).
-define(CMD_ACK_FRIEND,17).
-define(CMD_ACK_FRIEND_REPLY,18).
-define(CMD_DEL_FRIEND,20).
-define(CMD_DEL_FRIEND_REPLY,21).
-define(CMD_UPDATE_INFO,22).
-define(CMD_UPDATE_INFO_REPLY,23).
-define(CMD_CLEAR_UPDATE,24).
-define(CMD_CLEAR_UPDATE_REPLY,25).

-export([auth_on_register/5,
    auth_on_publish/6,
    auth_on_subscribe/3,
    on_client_wakeup/1,
    on_client_gone/1,
    on_client_offline/1,
    on_offline_message/5,
    on_register/3,
    on_subscribe/3,
    on_unsubscribe/3]).

on_client_gone({_MountPoint, ClientId} = _SubscriberId) ->
    error_logger:info_msg("on_client_gone: ~p", [ClientId]),
    ok.


on_client_wakeup({_MountPoint, ClientId} = _SubscriberId) ->
    error_logger:info_msg("on_client_wakeup: ~p", [ClientId]),
    ok.

on_offline_message({_MountPoint, ClientId} = _SubscriberId, _Qos, Topic, Payload, IsRetain) ->
    error_logger:info_msg("on_offline_message: ~p ~p ~p bytes ~p", [ClientId, Topic, byte_size(Payload), IsRetain]),
    ok.

on_client_offline({_MountPoint, ClientId} = _SubscriberId) ->
    error_logger:info_msg("on_client_offline: ~p", [ClientId]),
    ok.

on_register(_Peer, {_MountPoint, _ClientId} = _SubscriberId, _UserName) ->
%%    error_logger:info_msg("on_register: ~p ~p ~p", [Peer,ClientId,UserName]),
    ok.

on_subscribe(UserName, {_MountPoint, ClientId} = _SubscriberId, Topics) ->
    error_logger:info_msg("on_subscribe: ~p ~p ~p", [UserName, ClientId, Topics]),
    ok.

on_unsubscribe(UserName, {_MountPoint, ClientId} = _SubscriberId, Topics) ->
    error_logger:info_msg("on_unsubscribe: ~p ~p ~p", [UserName, ClientId, Topics]),
    ok.

auth_on_register({_IpAddr, _Port} = Peer, {_MountPoint, _ClientId} = SubscriberId, UserName, Password, CleanSession) ->
    error_logger:info_msg("auth_on_register: ~p ~p ~p ~p ~p", [Peer, SubscriberId, UserName, Password, CleanSession]),
    case UserName of
        <<"0">> when CleanSession == true -> %%新用户注册
            Key = hook_lib:md5_hex(binary_to_list(UserName) ++ ?AUTH_KEY),
            case Key == binary_to_list(Password) of
                true ->
                    {ok,UId} = db_uid_server:get_uid(),
                    Topic = integer_to_binary(UId),
                    task_worker:add_task(hook_lib,subscribe,[SubscriberId,Topic,0],1000),
                    task_worker:add_task(hook_lib,publish,[SubscriberId,Topic,<<?CMD_REGISTER,UId:?UINT>>,0],1500);
                false ->
                    {error, invalid_credentials}
            end;
        <<"0">> -> {error, invalid_credentials};
        _       ->
            Key = hook_lib:md5_hex(binary_to_list(UserName) ++ ?AUTH_KEY),
            case Key == binary_to_list(Password) of
                true ->
                    NewUserName = binary_to_list(UserName),
                    Topics = list_to_binary(io_lib:format("~s/#",[NewUserName])),
                    task_worker:add_task(hook_lib,subscribe,[SubscriberId,Topics,2],1000),
                    Topic = list_to_binary(NewUserName),
                    task_worker:add_task(hook_lib,publish,[SubscriberId,Topic,<<?CMD_LOGIN,?VERSION/binary>>,2],1500),
                    error_logger:info_msg("~p connected!", [UserName]),
                    ok;
                false ->
                    error_logger:info_msg("~p auth fail!", [UserName]),
                    {error, invalid_credentials}
            end
    end.

auth_on_publish(UserName, {MountPoint, _ClientId} = SubscriberId, QoS, Topic, Payload, IsRetain) ->
    error_logger:info_msg("auth_on_publish: ~p ~p ~p ~p [Payload size: ~p] ~p", [UserName, SubscriberId, QoS, Topic, erlang:size(Payload), IsRetain]),
    case Topic of
         [UserName,<<"command">>] ->
            %客服端提交命令  获取版本,加好友
            case Payload of
                <<?CMD_TOKEN,DeviceTokenAndOption/binary>> -> %注册更新token
                    MyUId = binary_to_integer(UserName),
                    db_worker:token_set(<<MyUId:?UINT>>,DeviceTokenAndOption),
                    {ok,<<?CMD_TOKEN,1>>};
                <<?CMD_ADD_FRIEND,UId:?UINT,Data/binary>> -> %UserName 请求 UId 加好友，并附带UserName用户资料
                    ToClientId = list_to_binary("c_"++integer_to_list(UId)),
                    ToTopic    = integer_to_binary(UId),
                    Ret = hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_ADD_FRIEND,Data/binary>>,QoS),
                    error_logger:info_msg("publish ~p ~p ~p ~p",[{MountPoint, ToClientId},ToTopic,QoS,Ret]),
                    {ok,<<?CMD_ADD_FRIEND_REPLY,1>>};
                <<?CMD_UNACK_FRIEND,UId:?UINT>> -> %告诉 UId UserName 拒绝加好友
                    MyUId = binary_to_integer(UserName),
                    ToClientId = list_to_binary("c_"++integer_to_list(UId)),
                    ToTopic    = integer_to_binary(UId),
                    hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_UNACK_FRIEND,MyUId:?UINT>>,QoS),
                    {ok,<<?CMD_UNACK_FRIEND_REPLY,1>>};
                <<?CMD_ACK_FRIEND,FriendUId:?UINT,Data/binary>> -> %UserName 同意 FriendUId 加好友，相互订阅
                    MyUId = binary_to_integer(UserName),
                    db_worker:add_friend(<<MyUId:32>>,FriendUId),
                    db_worker:add_friend(<<FriendUId:32>>,MyUId),
                    ToClientId = list_to_binary("c_"++integer_to_list(FriendUId)),
                    ToTopic    = integer_to_binary(FriendUId),
                    hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_ACK_FRIEND,Data/binary>>,QoS),
                    {ok,<<?CMD_ACK_FRIEND_REPLY,1>>};
                <<?CMD_SEND_MSG,FriendUId:?UINT,Data/binary>> ->
                    MyUId = binary_to_integer(UserName),
                    case db_worker:check_friend(<<MyUId:32>>,FriendUId) of
                        true ->   %是好友
                            ToClientId = list_to_binary("c_"++integer_to_list(FriendUId)),
                            ToTopic    = integer_to_binary(FriendUId),
                            hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_SEND_MSG,Data/binary>>,QoS),
                            case db_worker:token_get(<<FriendUId:32>>) of
                                <<Unread:?UBYTE,Op:?UBYTE,Token/binary>> when Op < 3 ->
                                    NewUnread = Unread + 1,
                                    case Op of
                                        1 -> push_worker_pool:push_notice(FriendUId,Token,<<230,148,182,229,136,176,228,184,128,230,157,161,229,138,160,229,175,134,230,182,136,230,129,175,239,188,129>>,NewUnread);
                                        2 -> push_worker_pool:push_notice(FriendUId,Token,<<>>,NewUnread)
                                    end,
                                    db_worker:token_set(<<FriendUId:32>>,<<NewUnread:?UBYTE,Op:?UBYTE,Token/binary>>);
                                _ -> ok
                            end,
                            {ok,<<?CMD_SEND_MSG_REPLY,1>>};
                        false ->  %不是好友
                            {ok,<<?CMD_SEND_MSG_REPLY,0>>}
                    end;
                <<?CMD_DEL_FRIEND,FriendUId:?UINT>> ->
                    MyUId = binary_to_integer(UserName),
                    case db_worker:check_friend(<<MyUId:32>>,FriendUId) of
                        true ->   %是好友,删除好友
                            db_worker:del_friend(<<MyUId:32>>,FriendUId),
                            db_worker:del_friend(<<FriendUId:32>>,MyUId),
                            ToClientId = list_to_binary("c_"++integer_to_list(FriendUId)),
                            ToTopic    = integer_to_binary(FriendUId),
                            hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_DEL_FRIEND,MyUId:32>>,QoS),  %告诉对方，某人删除你好友
                            {ok,<<?CMD_DEL_FRIEND_REPLY,1>>};
                        false ->  %不是好友
                            {ok,<<?CMD_DEL_FRIEND_REPLY,0>>}
                    end;
                <<?CMD_UPDATE_INFO,FriendUId:?UINT,Data/binary>> -> %更新用户信息 Retain = true
                    MyUId = binary_to_integer(UserName),
                    case db_worker:check_friend(<<MyUId:32>>,FriendUId) of
                        true ->   %是好友
                            ToClientId = list_to_binary("c_"++integer_to_list(FriendUId)),
                            ToTopic    = [integer_to_binary(FriendUId),integer_to_binary(MyUId)],  % uid/friendid
                            error_logger:info_msg("publish to ~p",[ToTopic]),
                            hook_lib:publish({MountPoint, ToClientId},ToTopic,<<?CMD_UPDATE_INFO,Data/binary>>,QoS,true),
                            {ok,<<?CMD_UPDATE_INFO_REPLY,1>>};
                        false ->  %不是好友
                            {ok,<<?CMD_UPDATE_INFO_REPLY,0>>}
                    end;
                <<?CMD_CLEAR_UPDATE,FriendUId:?UINT>> ->
                    MyUId = binary_to_integer(UserName),
                    ToClientId = list_to_binary("c_"++integer_to_list(MyUId)),
                    ToTopic    = [integer_to_binary(MyUId),integer_to_binary(FriendUId)],
                    hook_lib:publish({MountPoint, ToClientId},ToTopic,<<>>,QoS,true),
                    {ok,<<?CMD_CLEAR_UPDATE_REPLY,1>>};
                _ -> ok
            end;
        [UserName,<<"info">>] ->   ok; % 更新自己资料
        true -> {error, whatever}
    end.

auth_on_subscribe(UserName, ClientId, [{_Topic, _QoS} | _] = Topics) ->
    error_logger:info_msg("auth_on_subscribe: ~p ~p ~p", [UserName, ClientId, Topics]),
    {error,whatever}.
